package com.yuanian.timingwheel.model.timingwheel;

import com.yuanian.timingwheel.context.ExecutionContext;
import com.yuanian.timingwheel.context.SpringContext;
import com.yuanian.timingwheel.distributedlock.DbDistributedLock;
import com.yuanian.timingwheel.distributedlock.DistributedLock;
import com.yuanian.timingwheel.exception.TwException;
import com.yuanian.timingwheel.infrastructure.DateUtils;
import com.yuanian.timingwheel.model.task.TaskStatus;
import com.yuanian.timingwheel.model.task.base.BaseTask;
import com.yuanian.timingwheel.model.timingwheel.bucket.Bucket;
import com.yuanian.timingwheel.model.timingwheel.bucket.factory.BucketFactory;
import com.yuanian.timingwheel.model.timingwheel.bucket.factory.BucketMapFactory;
import com.yuanian.timingwheel.redis.RedisMap;
import com.yuanian.timingwheel.redis.RedisServiceImpl;
import com.yuanian.timingwheel.redis.RedisSet;
import com.yuanian.timingwheel.respository.po.LivingServerEntity;
import com.yuanian.timingwheel.respository.po.TaskEntity;
import com.yuanian.timingwheel.statistics.LivingServer;
import com.yuanian.timingwheel.threadpool.ThreadInterruptReason;
import com.yuanian.timingwheel.threadpool.ThreadPoolExecutorFacade;
import com.yuanian.timingwheel.threadpool.ThreadStatus;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 日级时间轮
 */
public class DayTimingWheel extends TaskLoader {

    //一天有24*60*60秒钟
    private int secondNum = 24 * 60 * 60;

    //秒上的槽，槽上放任务的队列,红黑树
    //private TreeMap<Integer, Bucket> buckets;

    private RedisMap<Integer, Bucket> buckets;

    //槽的工厂
    private BucketFactory bucketFactory;

    //启动标识
    private volatile boolean startFlag = false;

    //核心线程
    protected Thread mainThread;

    //当前小时
    private int hour = 0;

    //当前分钟
    private int minute = 0;

    //当前秒
    private int second = 0;

    //已经加载了任务的日期集合
    //private Set<Long> loadDbDateSet;

    private RedisSet<Long> loadDbDateSet;

    //最后一次响应时间
    private Date lastRespondTime;

    //是否第一次秒推进，第一次忽略卡顿
    private boolean firstSecondChange = true;

    //锁
    private Object lock;

    //下一个任务运行在几秒
    private volatile Integer nextTaskRunSecond;

    //距离0点已经流逝的秒钟总数
    private int passedSecond;

    //距离0点已经流逝的分钟总数
    private int passedMinute;

    //当前激活的服务器
    private LivingServer livingServer;

    //分布式锁的实现
    private DistributedLock<LivingServer> distributedLock;

    //运行的当天
    protected Date day;

    //主线程的状态
    private volatile ThreadStatus threadStatus;

    public DayTimingWheel() {

        //buckets = new TreeMap();

        buckets = new RedisMap<>("load");

        bucketFactory = new BucketMapFactory();

        //loadDbDateSet = new HashSet<>();
        loadDbDateSet = new RedisSet<>("loadDbDate");

        lock = new Object();

        distributedLock = new DbDistributedLock();

        threadStatus = ThreadStatus.CLOSE;

        mainThread = new Thread(() -> {
            //分布锁锁住，创建服务器,防止两台机器同时启动
            livingServer = distributedLock.lock("schedule_lock", () -> {
                //存在正在运行的机器
                if (LivingServerEntity.existLivingServer()) {
                    return null;
                }
                return LivingServerEntity.createLivingServer();
            });
            //没有获取运行的权利
            if (livingServer == null) {
                return;
            }
            ExecutionContext.getInstance().setLivingServer(livingServer);
            ExecutionContext.getInstance().setDayTimingWheel(this);
            //扫描自动声明式定时任务
            try {
                scan();
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }
            while (startFlag) {
                //从数据库中加载任务
                try {
                    lastRespondTime = loadTaskFromDB();
                } catch (TwException e) {
                    continue;
                }
                //获取当前的秒钟
                int nowPassedSecond = getNowPassedSecond();
                if (nowPassedSecond != passedSecond) {//秒推进
                    doSecondChange(nowPassedSecond);
                }
                //设置当前秒
                //this.passedSecond = cpuOptimize(nowPassedSecond);
                this.passedSecond = getNowPassedSecond();
                try {
                    TimeUnit.MILLISECONDS.sleep(DateUtils.restSecond());
                } catch (InterruptedException e) {

                }
            }
        });
    }

    private int getNowPassedSecond() {
        //距离0点已经流失的秒总数
        int nowPassedSecond = DateUtils.dayPassSecondShort(lastRespondTime, this.day);
        //距离0点已经流失的分总数
        passedMinute = nowPassedSecond / 60;
        //当前小时
        hour = passedMinute / 60;
        //当前分钟
        minute = passedMinute - hour * 60;
        //当前秒
        second = nowPassedSecond - passedMinute * 60;
        return nowPassedSecond;
    }

    /**
     * 加载当日的任务
     */
    private Date loadTaskFromDB() throws TwException {
        //线程池必须没有任务在执行，防止重复执行
        Date result;
        Date now = new Date();
        long zeroDayLong = DateUtils.zeroDay(now);
        if (!loadDbDateSet.contains(zeroDayLong)) {
            day = now;
            this.threadStatus = ThreadStatus.LOAD;
            loadDbDateSet.add(zeroDayLong);
            //清理时间轮数据
            //SpringContext.getBean(RedisServiceImpl.class).clearBucket();
            buckets.clear();
            //清理秒分布数据
            loadRequiredRunTask(now);
            //SQL查询需要时间，所以要产生一个最新的时间，对于23：58加载任务，当第二天0点1分SQL才运行完，有BUG需解决
            result = new Date();
            day = result;
            if (DateUtils.zeroDay(now) != DateUtils.zeroDay(result)) {
                throw new TwException("加载的数据和运行时间不正确");
            }
            //把数据放入REDIS中
            //SpringContext.getBean(RedisServiceImpl.class).init(this.buckets);
            this.threadStatus = ThreadStatus.RUN;
        } else {
            result = new Date();
            if (day == null) {
                day = result;
            }
        }
        return result;
    }

    /**
     * 对cpu的优化,1.智能检测当天的任务是否全部完成，如果完成就休眠让出CPU，一直到新增当天的任务，才唤醒去执行
     * 2.智能检测下一个任务执行时间，休眠到下一个任务执行时间让出CPU,而不是每秒去检测。
     */
    private int cpuOptimize(int nowPassedSecond) {
        //得到下一个任务的执行时间，如果超过了3秒，并且没有补偿的任务可做了，就休眠
      /*  nextTaskRunSecond = buckets.higherKey(nowPassedSecond);
        if (nextTaskRunSecond != null && nextTaskRunSecond - nowPassedSecond >= 3 && beforeTaskIsCompleted(nowPassedSecond)) {
            threadSleep((nextTaskRunSecond - nowPassedSecond - 1) * 1000, "3秒内没任务，并且以前任务全部完成", ThreadStatus.SHORT);
            return DateUtils.dayPassSecondShort(new Date(), this.day);
        } else if ((nextTaskRunSecond == null || (nextTaskRunSecond != null && nextTaskRunSecond - nowPassedSecond >= 3)) && !beforeTaskIsCompleted(nowPassedSecond)) {//没有下一个任务正好做补偿任务
            compensateExecuteBeforeTask(nowPassedSecond);
            return nowPassedSecond;
        } else if (nextTaskRunSecond == null && allTaskIsCompleted()) {//没有下一个任务，并去任务全部执行了
            threadSleep(DateUtils.dayRestSecond(lastRespondTime), "任务全部完成", ThreadStatus.LONG);
            return DateUtils.dayPassSecondShort(new Date(), this.day);
        }*/
        return nowPassedSecond;
    }

    private void threadSleep(long second, String sleepReason, ThreadStatus type) {
        try {
            System.out.println("任务调度主线程进入休眠，休眠时间为：" + second / 1000 + "秒" + ",休眠原因为：" + sleepReason);
            this.threadStatus = type;
            Thread.sleep(second);
        } catch (InterruptedException e) {
            System.out.println("任务调度主线程被唤醒");
            this.threadStatus = ThreadStatus.RUN;
        }
    }

    /**
     * 补偿执行以前没有执行过的任务
     */
    private void compensateExecuteBeforeTask(int nowPassedSecond) {
        //运行以前分钟的数据
        for (int i = nowPassedSecond - 1; i >= 0; i--) {
            Bucket secondTimingWheel = getBucket(i);
            if (secondTimingWheel != null) {
                secondTimingWheel.executeTask();
            }
        }
    }

    /**
     * 秒推进的业务逻辑
     * @param
     * @param
     */
    private void doSecondChange(int nowPassedSecond) {
        System.out.println("任务调度当前时间为:" + hour + "小时" + minute + "分钟" + second + "秒");
        //间隔时间,理想情况为1秒
        int num = nowPassedSecond - passedSecond;
        Bucket secondTimingWheel = null;

        Map<Integer, Bucket> map = buckets.map();

        for (int i = 0; i < num; i++) {
            if (i != 0 && !firstSecondChange) {//卡顿,i!=0说明卡顿了
                livingServer.incDelaySeconds();
            }
            int sec = nowPassedSecond - i;
            //System.out.println(i);
            secondTimingWheel = map.get(sec);
            if (secondTimingWheel != null) {
                secondTimingWheel.executeTask();
            }
        }
        if (num > 2) {//卡顿后马上更新
            livingServer.refresh(this.lastRespondTime);
        } else {
            if (second % 10 == 0) {//10秒更新一下
                livingServer.refresh(this.lastRespondTime);
            }
        }
        firstSecondChange = false;
    }

    /**
     * 加载当天12点以前执行的任务
     */
    private void loadRequiredRunTask(Date date) {
        //获取当前23:59:59之前的任务
        List<TaskEntity> list = TaskEntity.getEndOfDayTask(date);
        System.out.println("任务调度加载任务数量：" + list.size());
        for (TaskEntity taskEntity : list) {
            BaseTask task = taskEntity2BaseTask(taskEntity);
            task.setTaskStatus(TaskStatus.LOADED);
            loadTaskFromDB(task);
            livingServer.getLoadTasks().put(task.getId(), task);
        }
    }

    /**
     * 等待正常的服务
     */
    protected void waitValidServer() {
        //服务没启动或正在加载任务
        while (this.threadStatus == ThreadStatus.LOAD || this.threadStatus == ThreadStatus.CLOSE || !startFlag) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 把任务加入到时间轮或者直接执行
     * @param task
     * @param round
     */
    protected void addTaskToTwOrExecuteFromDB(BaseTask task, Integer round) {
        if (round == null) {
            return;
        }
        if (round >= 0) {
            getAndSetBucket(round).push(task);
            //状态为暂停
            if (task.getStatus() == 1) {
                getAndSetBucket(round).suspendTaskId(task.getId());
            }

        } else if (round < 0) {//过期任务，马上执行
            //状态为正常s
            if (task.getStatus() == 0) {
                ThreadPoolExecutorFacade.getInstance().execute(task);
            }
        }
    }

    /**
     * 装载任务到时间轮
     * @param task
     */
    public void loadTaskFromDB(BaseTask task) {
        //得到任务分钟盘
        Integer round = getBucketNum(task);
        addTaskToTwOrExecuteFromDB(task, round);
    }

    /**
     * 装载任务到时间轮
     * @param task
     */
    public void loadTask(BaseTask task) {
        //得到任务分钟盘
        Integer round = getBucketNum(task);
        addTaskToTwOrExecute(task, round);
    }

    /**
     * 把任务加入到时间轮或者直接执行
     * @param task
     * @param round
     */
    protected void addTaskToTwOrExecute(BaseTask task, Integer round) {
        if (round == null) {
            return;
        }
        if (round >= 0) {
            livingServer.getLoadTasks().put(task.getId(), task);
            interruptMainThread(round, ThreadInterruptReason.ADDNEWTASK);
            synchronized (lock) {
                getAndSetBucket(round).push(task);
            }
        } else if (round < 0) {//过期任务，马上执行
            ThreadPoolExecutorFacade.getInstance().execute(task);
        }
    }

    /**
     * 唤醒主线程
     */
    protected synchronized void interruptMainThread(int round, ThreadInterruptReason threadInterruptReason) {

        if (this.threadStatus == ThreadStatus.LONG || this.threadStatus == ThreadStatus.SHORT) {
            if (nextTaskRunSecond == null || round < nextTaskRunSecond) {
                nextTaskRunSecond = round;
                System.out.println("打断任务调度主线程，打断原因为：" + threadInterruptReason);
                mainThread.interrupt();
                this.threadStatus = ThreadStatus.RUN;
            }
        }
    }

    protected Bucket getAndSetBucket(int round) {
        if (round < 0 && round > (secondNum - 1)) {
            return null;
        }
        //Bucket secondTimingWheel = SpringContext.getBean(RedisServiceImpl.class).getBucket(round);
        Bucket secondTimingWheel = buckets.get(round);
        if (secondTimingWheel == null) {
            secondTimingWheel = bucketFactory.getBucket(round);
            buckets.put(round, secondTimingWheel);
            //SpringContext.getBean(RedisServiceImpl.class).putBucket(round, secondTimingWheel);
        }
        return secondTimingWheel;
    }

    protected Bucket getBucket(int round) {
        if (round < 0 && round > (secondNum - 1)) {
            return null;
        }
        //return SpringContext.getBean(RedisServiceImpl.class).getBucket(round);
        return buckets.get(round);
    }

    /**
     * 获取任务的轮次
     * @param task
     * @return
     */
    private Integer getBucketNum(BaseTask task) {
        return getBucketNum(task.getTaskInfo().getExecutionTime(), this.day);
    }

    /**
     * 获取任务的轮次
     * @param executionDate
     * @return
     */
    protected Integer getBucketNum(Date executionDate, Date date) {
        //和当天0点相比较,获取间隔的秒钟数
        long restTime = DateUtils.dayPassSecond(executionDate, date);
        if (restTime >= secondNum) {//不是当天的任务,是以后的任务
            return null;
        }
        if (restTime < 0) {//是当天之前的任务
            return -1;
        }
        return (int)restTime;
    }

    public void start() {
        startFlag = true;
        mainThread.setDaemon(true);
        mainThread.setPriority(Thread.MAX_PRIORITY);
        mainThread.setName("htliu-job, dayTimingWheel");
        mainThread.start();
    }

    public void suspend() {
        if (startFlag) {
            startFlag = false;
            threadSleep(Long.MAX_VALUE, "任务调度服务器暂停", ThreadStatus.LONG);
        }
    }

    public void resume() {
        if (!startFlag) {
            startFlag = true;
            interruptMainThread(0, ThreadInterruptReason.RESUMESERVER);
        }
    }

    public void shutdown() {
        if (startFlag) {
            startFlag = false;
        }
        mainThread = null;
    }

    /**
     * 是否完成了当日的所有任务
     * @return
     */
    public boolean allTaskIsCompleted() {
       /* for (Map.Entry<Integer, Bucket> bucket : buckets.entrySet()) {
            if (bucket != null) {
                if (!bucket.getValue().isCompleted()) {
                    return false;
                }
            }
        }*/
        return true;
    }

    /**
     * 当前秒之前的任务是否完成了
     * @return
     */
    public boolean beforeTaskIsCompleted(int nowPassedSecond) {
      /*  for (Map.Entry<Integer, Bucket> bucket : buckets.entrySet()) {
            if (bucket.getKey() <= nowPassedSecond && bucket != null) {
                if (!bucket.getValue().isCompleted()) {
                    return false;
                }
            }
        }*/
        return true;
    }

    public RedisMap<Integer, Bucket> getBuckets() {
        return buckets;
    }

    public static void main(String[] args) {
        HashMap h = new HashMap();
        System.out.println(h.put(1,1));
    }

}
